home *** CD-ROM | disk | FTP | other *** search
- Extra Memory for BASIC
- (PC Magazine Vol 5 No 8 Apr 29, 1986 PC Tutor)
-
- A programmer is trying to develop a multipage screen-buffer
- technique for a monochrome display that will provide a function
- similar to the color adapter's ability to keep several different
- screen images stored in memory and flip them to the screen when
- appropriate. Using an assembler routine, he is unable to use the
- highest 12K when running with the BASIC interpreter. However, when
- the same program is run in compiled form (with appropriate changes
- such as CALL ABSOLUTE), the machine goes beserk. Is there a way to
- do this using the memory-management DOS function calls (48h, 49h,
- and 4Ah) in DOS 2.1?
-
- Editor's Response:
-
- Programs compiled under the old IBM and Microsoft BASIC compilers
- use all available memory from the beginning of the program upward.
- Since these compilers were designed to work under DOS 1.1, they do not
- free up any of this program memory using function call 4Ah. Conse-
- quently, a function call 48h from within an assembly language routine
- will not allocate a memory block since there is no memory left to
- allocate. Nor could you first deallocate some of the compiled BASIC's
- memory with an assembler subroutine using function call 4Ah, because
- the program has already set itself up in the memory and expects it all
- to be usable. There are a couple of solutions to the problem, however.
- Under the BASIC compiler you could define a string variable 4000
- bytes long to hold the monochrome display contents with:
-
- MONOHOLD$ = STRING$(4000,0)
-
- You could then use VARPTR to pass the address of this string to your
- assembly language subroutine. (Note that the first 2 bytes pointed
- to with VARPTR(MONOHOLD$) contain the length of the string, which is
- 4000.) However, this would not work under the BASIC interpreter,
- whose strings are limited to 255 bytes.
- If you'd like to keep the compiled and interpreted BASIC programs
- consistent in the way they store data in upper memory, therefore, you
- need to prevent the compiled BASIC program from taking up all of
- available memory. This can be done by modifying some of the header
- information in the compiled .EXE run file, though, as you'll see there
- may be some problems in this approach.
- Every .EXE file begins with header information DOS needs to load
- the program into memory, perform segment fix-ups, and allocate space
- for it to run. This header information is documented in the DOS
- manuals for versions 2.0 and earlier and in the DOS Technical Reference
- manuals for versions 2.1 and later.
- You can't see this header information if you load the .EXE file
- directly into DEBUG, because DEBUG uses the header to perform all the
- space allocation and fix-ups, making the program all ready to run.
- You'll have to first rename the file to an extension other than .EXE
- and then load it into DEBUG. (But then you won't be able to run it
- in DEBUG.)
-
-
- Use the following commands to look at the first part of an .EXE
- file header:
-
- RENAME runfile.EXE runfile.XXX
- DEBUG
- -N runfile.XXX
- -L 0
- -D 0
-
- The 2-byte word beginning at offset 000Ah is defined in the DOS
- Technical Reference manual as the "minimum number of 16-byte paragraphs
- required above the end of the loaded program." This word will often
- be at offset 0000h. The 2-byte word at offset 000Ch is the "maximum
- number of 16-byte paragraphs required above the end of the loaded
- program." This will usually be FFFFh, which means that the program
- wants all available memory above where it loads.
- This extra memory space is used for the "heap" and the "stack."
- During calculations, for instance, the stack is used to store inter-
- mediate results. The heap is used by the program mostly for dynamic
- storage. If your program executes a STRING$ command such as shown
- above, or if it DIMensions an array, the result has to go somewhere.
- It goes in the heap. In a program that does a lot of dynamic string
- and array allocation, the heap can get pretty cluttered up and
- disorganized. At times, normal execution can grind to a halt while
- the program cleans up the heap in a process technically referred to
- as "garbage collection."
- Of course, most compiled BASIC programs don't really need all
- available memory and can function normally with less. Thus, you can
- change the "maximum memory" word at offset 000Ch in the .EXE header
- to something lower than FFFFh to prevent DOS from allocating all
- available memory to the program.
- Let's assume, for example, that you have 256K memory in your
- machine and that the bottom 128K memory is taken up by DOS and
- resident programs. (You get this second figure by subtracting the
- "bytes free" from the "bytes total memory" information provided by
- CHKDSK.) If your .EXE program is about 32K, you can give it another
- 64K when it runs and still have 32K left over at the top of memory
- for your screen swaps.
- To do this, you'd enter the following DEBUG commands as a direct
- continuation of the program above (whose last line was: -D 0):
-
- -E 000C 00 10
- -W
- -Q
- RENAME runfile.XXX runfile.EXE
-
- Note that the 2 bytes (00 and 10) specified after the E (Enter) command
- are in reverse order, as usual. The actual word you've entered is
- 1000h or 4096 decimal. This is the number of 16-byte paragraphs, and
- it corresponds to 64K bytes.
- When you now run your .EXE program, the DOS loaders uses this
- "maximum memory" word in the .EXE header to computer a "top of memory"
- word (also in paragraph form) that it puts in offset 0002h of the
- programs Program Segment Prefix. The compiled BASIC prologue uses
- this value to limit the amount of memory it uses.
- You can look at the Program Segment Prefix by loading the .EXE
- file (not the renamed .XXX file) into DEBUG and executing the command:
-
- -D 0
-
- If an .EXE header has FFFFh in offset 000Ch, the Program Segment Prefix
- top of memory word at 0002h will be 4000h for a 256K machine and A000h
- for a 640K machine (again, you'll see the 2 bytes shown in reverse
- order). If the amount of real available memory exceeds the length of
- the program plus the additional memory (plus the memory taken up by
- DEBUG), then you'll see a lower value. If you have enough high memory
- for screen swaps while in DEBUG, you'll certainly have enough when
- running the program outside of DEBUG.
- How low can you set the value at 000Ch in the .EXE header?
- Definitely don't set it to 0 -- that indicates to DOS that the program
- is to be loaded high in memory, and it will crash. With some compiled
- BASIC programs, it can be set as low as 0060h paragraphs (about 1.5K
- bytes). If you have a lot of dynamic string and array storage in your
- program, you'll need something higher, or you'll probably get an "out
- of memory" error during program execution.
- Note also that the program may run slower, since it has to perform
- more frequent garbage collections to clean up the heap. You'll probably
- have to experiment to get an optimum value.
- If you do your compilation and linking using a batch file with a
- replaceable parameter for the filename, you can automate this .EXE
- header modification with the following batch file code after the LINK:
-
- RENAME %1.EXE %1.XXX
- DEBUG %1.XXX < HEADCHG
- RENAME %1.XXX %2.EXE
-
- where HEADCHG is a file containing the DEBUG commands:
-
- E 010C 00 10
- W
- Q
-
- Note here that you have to use 0100h plus the header offset for the
- E (Enter) command because DEBUG is loading the file at offset 0100h.
- The .EXE file header also contains a word "checksum" value, which
- is supposed to be used by the DOS loader to ensure .EXE file integrity.
- If we change a value in the header, then the checksum will not check.
- However, DOS doesn't use this checksum when loading programs. If it
- does in the future, you'll have to make an adjustment to that value as
- well.
- You may also be interested to learn that the LINK program included
- with the Microsoft (but not the IBM) Macro Assembler and the Microsoft
- C Compiler includes a switch called CPARMAXALLOC to set the maximum
- memory value during the LINK so you don't have to adjust it later.
- The next version of the Microsoft C Compiler also has an EXEMOD program
- to change this header information with a one-line command.
- (Persons tired of having COMMAND.COM reload after running a
- compiled BASIC program may alter this word to prevent the program from
- overwriting the transient part of COMMAND.COM at high memory.)
-
- Although there's nothing wrong with modifying this part of the .EXE
- header, you may have problems using memory above the loaded program.
- First, the value you pick to go in the .EXE header will probably
- be dependent on your machine's memory configuration. If it's run on a
- machine with a few more resident programs loaded, you may not have
- enough high memory to store you screen swaps.
- Second, in a multitasking system such as TopView, this solution
- will be absolutely disastrous, because your program is using memory
- that doesn't belong to it. Only memory up to that top of memory word
- in the Program Segment Prefix is allocated for your program, and you
- are deliberately using memory higher than that. TopView may be using
- this memory for some other program. The same problem exists if you
- use high memory in the BASICA interpreter.
- You also don't want to write over the first few bytes right after
- the top of the extra memory, because these contain a marker DOS uses
- for memory allocation.
- There is another solution, but it would require using a separate
- assembly language program to allocate memory below your loaded program.
- This assembly language program would perform the following four steps:
- 1) deallocate all memory above its code by executing the DOS 4Ah
- function call;
- 2) allocate a block of memory for screen swaps (or BLOADs or
- whatever) with function call 48h;
- 3) store the address of this block in lower memory, probably in
- one of the user interrupt vectors beginning at 0000:0180h; and,
- 4) load the compiled BASIC program (or the BASICA interpreter)
- using the EXEC function call 4Bh.
- The BASIC program -- in either interpreted or compiled form --
- could retrieve the address of this allocated memory block using PEEK
- and pass this address to the assembly language subroutine. When the
- BASIC program terminates, control would pass back to the assembler
- program that loaded it, which would then terminate.
-
-
-
- -----------------------------------------------------------------
- Minding Memory From BASIC
- (COMPUTE! Magazine June 1986 by D. W. Neuendorf)
-
- Memory management in DOS has become an important issue. The new
- desktop tools and coresident programs are designed to wait in the
- background to be called during the operation of another program. A
- number of these utilities may be lurking in memory at once, and
- programmers can't predict which other programs will be present with
- their own. The result can be memory conflicts and system crashes.
- DOS 2.0 and later versions contain several function calls designed
- to give the operating system control over how the computer's memory is
- divided among programs residing in memory simultaneously. The most
- basic of these functions simply attempt to allocate and deallocate
- blocks of memory at a program's request. These DOS calls are readily
- available to machine language programmers, just like all other machine-
- level resources.
- BASIC programmers, on the other hand, have no direct access to
- many DOS functions. But there are ways for BASIC programs to call on
- DOS to perform these memory management tasks.
- There are two DOS functions we're interested in -- one for
- allocating memory and another for deallocating memory.
- In machine language, both functions are called by placing a
- function number in the microprocessor's AH register and calling
- Interrupt 21h. (Function numbers indicated to DOS which function is
- being called. The interrupt then performs the function.) The numbers
- are 48h for the allocate function and 49h for the deallocate function.
- In addition to these numbers, each function call requires that
- you pass an argument. The allocate function requires the number of
- 16-byte paragraphs of memory to be allocated. This number must be
- placed in the microprocessor's BX register. The deallocate function
- requires the segment address of a block to be deallocated. This
- number must be placed in the ES register.
- After each function is performed, it returns a value. The
- allocate function returns, via the AX register, either the segment
- address of an allocated block or an error code (7 or 8 plus a set
- carry bit) if the function was unsuccessful. The deallocation routine
- returns nothing if successful, but sets the carry bit and returns an
- error code (7 or 9) if unsuccessful. The machine language listings
- below show the assembler code necessary to call these functions.
- The BASIC program shows how to call these functions from BASIC.
- Since the allocate routine is not available initially and therefore
- can't allocate space for itself, the program reserves a few bytes for
- it just above BASIC (using the CLEAR statement in line 10). Once the
- allocate routine has been installed (lines 40-60), it can be used to
- get memory from DOS for machine language routines and other data. An
- example of its use is the call in line 70, which gets the segment
- address of a memory block for the deallocate routine. Finally, line
- 120 shows an example of using the deallocate routine -- it deallocates
- its own memory.
- After studying the BASIC program, you'll see that it's possible
- to put a machine language subroutine outside BASIC's 64K memory area,
- thus saving some space for BASIC programs. Better yet, you don't have
- to worry about where in memory you're hiding the routine -- DOS takes
- care of it. If you use a lot of machine language subroutines or
- store large amounts of data in memory, you'll have a lot more room to
- work with if you don't have to put everything inside BASIC's own
- segment.
- If everyone relies on DOS to determine where their programs reside
- in memory, we can all feel confident that our coresident programs are
- not overlapping and conflicting with each other. But if too many
- programmers bypass these DOS functions, the rest of us won't dare to
- rely on them, either. After all, DOS can protect only the data or
- programs that it knows about.
-
-
-
- DOS Memory Allocation:
-
- page 50,132
- 0000 alloc segment para
- assume cs:alloc
- assume ds:alloc
- assume es:alloc
- 0000 allocate proc far
- ;
- ;Routine to allow BASIC to make DOS call to allocate
- ;a block of memory outside of BASIC's own segment.
- ;CALL ALLOC(MEMORY) - when BASIC calls the routine,
- ;MEMORY contains the number of bytes to be allocated.
- ;When the routine returns to BASIC, MEMORY contains
- ;the segment address of the allocated block of memory.
- ;A 7 or 8 indicates allocation failed.
- ;
- 0000 55 push bp
- 0001 8B EC mov bp,sp
- 0003 8B 5E 06 mov bx,[bp+6] ;get address of MEMORY
- 0006 8B 1F mov bx,[bx] ;get number of bytes to be allocated
- 0008 B4 48 mov ah,48h ;DOS function number
- 000A CD 21 int 21h ;DOS call itself
- 000C 8B 5E 06 mov bx,[bp+6] ;address of MEMORY
- 000F 89 07 mov [bx],ax ;put segment address of allocated
- memory in MEMORY
- 0011 5D pop bp
- 0012 CA 0002 ret 2
- ;
- 0015 allocate endp
- 0015 alloc ends
- end
-
-
- DOS Memory Deallocation:
-
- page 50,132
- 0000 dealloc segment para
- assume cs:dealloc
- assume ds:dealloc
- assume es:dealloc
- 0000 dlc proc far
- ;
- ;Routine to allow BASIC to make DOS call to deallocate
- ;a block of memory previously allocated using ALLOC.
- ;CALL DEALLOC(MEMORY) - when BASIC calls the routine,
- ;MEMORY contains the segment address of the block of
- ;memory to be dealloc. When the routine returns to
- ;BASIC, MEMORY contains either the original segment
- ;address or an error code. A 7 or 9 indicates
- ;allocation failed.
- ;
- 0000 55 push bp
- 0001 06 push es
- 0002 8B EC mov bp,sp
- 0004 8B 5E 06 mov bx,[bp+8] ;get address of MEMORY
- 0007 8E 07 mov es,[bx] ;get segment address of block to be
- deallocated
- 0009 B4 49 mov ah,49h ;DOS function number
- 000B CD 21 int 21h ;DOS call itself
- 000D 8B 5E 06 mov bx,[bp+8]
- 0010 89 07 mov [bx],ax ;put error code in MEMORY
- 0012 07 pop es
- 0013 5D pop bp
- 0014 CA 0002 ret 2
- ;
- 0017 dlc endp
- 0017 dealloc ends
- end
-
-
- DOS Memory Functions in BASIC:
-
- 10 CLEAR ,&HFFDF 'Reserve a few bytes just above BASIC for alloc routine
- 20 DEFINT A-Z
- 30 DEF SEG:ALLOC=&HFFDF:DMEMORY=2:DEALLOC=0
- 40 RESTORE 50:FOR X=0 TO 20:READ Y:POKE X+ALLOC,Y:NEXT 'Install alloc.
- 50 DATA &h55,&h8b,&hec,&h8b,&h5e,&h06,&h8b,&h1f,&hb4,&h48,&hcd
- 60 DATA &h21,&h8b,&h5e,&h06,&h89,&h07,&h5d,&hca,&h02,&h0
- 70 CALL ALLOC(DMEMORY) 'DOS call to allocate memory for dealloc routine
- 80 DEF SEG=DMEMORY
- 90 RESTORE 100:FOR X=0 TO 22:READ Y:POKE X,Y:NEXT 'Install dealloc
- 100 DATA &h55,&h06,&h8b,&hec,&h8b,&h5e,&h08,&h8e,&h07,&hb4,&h49,&hcd
- 110 DATA &h21,&h8b,&h5e,&h08,&h89,&h07,&h07,&h5d,&hca,&h02,&h00
- 120 CALL DEALLOC(DMEMORY)
- 130 END
-
-